#include <QtGui>

#include "PasHighlighter.h"

PasHighlighter::PasHighlighter(QTextDocument* parent)
    : QSyntaxHighlighter(parent)
{
    HighlightingRule rule;
    QColor color;

    _config = new QSettings(QSettings::defaultFormat(), QSettings::UserScope,
                            qApp->organizationName(), "pas.syntax");

    //! compiler directives
    color = _config->value("compiler_directive/color", QColor(Qt::darkBlue).name()).toString();
    _compilerDirectiveFormat.setForeground(color);
    rule.pattern = QRegExp("$[A-Za-z]+");
    rule.format = _compilerDirectiveFormat;
    _highlightingRules.append(rule);

    //! numbers
    color = _config->value("number/color", QColor(Qt::darkBlue).name()).toString();
    _numberFormat.setForeground(color);
    rule.pattern = QRegExp("\\b[0-9]+\\b");
    rule.format = _numberFormat;
    _highlightingRules.append(rule);

    //! keywords
    color = _config->value("keyword/color", QColor(Qt::darkYellow).name()).toString();
    _keywordFormat.setForeground(color);
    QVariantList keywords;
    keywords << "absolute" << "and" << "array" << "asm" << "assembler"
             << "begin" << "case" << "const" << "constructor" << "destructor"
             << "div" << "do" << "downto" << "else" << "end" << "export"
             << "exports" << "external" << "far" << "file" << "for"
             << "forward" << "function" << "goto" << "if" << "implementation"
             << "in" << "index" << "inherited" << "inline" << "interface"
             << "interrupt" << "label" << "library" << "mod" << "name"
             << "near" << "nil" << "not" << "object" << "of" << "or"
             << "overload" << "override" << "packed" << "private"
             << "procedure" << "program" << "public" << "record" << "repeat"
             << "resident" << "set" << "shl" << "shr" << "string" << "then"
             << "to" << "type" << "unit" << "until" << "uses" << "var"
             << "virtual" << "while" << "with" << "xor";
    _keywords = _config->value("keyword/list", keywords).toList();
    foreach (const QVariant& keyword, _keywords) {
        rule.pattern = QRegExp(QString("\\b%1\\b").arg(keyword.toString()));
        //! pascal is case insensitive
        rule.pattern.setCaseSensitivity(Qt::CaseInsensitive);
        rule.format = _keywordFormat;
        _highlightingRules.append(rule);
    }

    /*!
     * function
     * function goes before class, otherwise class constructor will be
     * recognized as function.
     */
    color = _config->value("function/color", QColor(Qt::blue).name()).toString();
    _functionFormat.setForeground(color);
    rule.pattern = QRegExp("\\b[A-Za-z0-9_]+(?=\\()");
    rule.format = _functionFormat;
    _highlightingRules.append(rule);

    //! class
    color = _config->value("class/color", QColor(Qt::darkMagenta).name()).toString();
    _classFormat.setForeground(color);
    rule.pattern = QRegExp("\\bT[A-Za-z]+\\b");
    rule.format = _classFormat;
    _highlightingRules.append(rule);

    //! quotation
    color = _config->value("quotation/color", QColor(Qt::darkGreen).name()).toString();
    _quotationFormat.setForeground(color);
    rule.pattern = QRegExp("(\".*\"|<.*>)");
    rule.format = _quotationFormat;
    _highlightingRules.append(rule);

    //! singleline comment
    color = _config->value("singleline_comment/color", QColor(Qt::darkGreen).name()).toString();
    _singleLineCommentFormat.setForeground(color);
    rule.pattern = QRegExp("//[^\n]*");
    rule.format = _singleLineCommentFormat;
    _highlightingRules.append(rule);

    /*!
     * multiline comment (comment block)
     * \sa further process goes to highlightBlock()
     */
    color = _config->value("multiline_comment/color", QColor(Qt::darkGreen).name()).toString();
    _multiLineCommentFormat.setForeground(color);
    _commentStartExpression = QRegExp("\\{)|(\\(\\*");
    _commentEndExpression = QRegExp("(\\})|\\*\\))");

    saveSettings();
}

PasHighlighter::~PasHighlighter()
{
    delete _config;
}

void PasHighlighter::saveSettings()
{
    _config->setValue("compiler_directive/color", _compilerDirectiveFormat.foreground().color().name());
    _config->setValue("number/color", _numberFormat.foreground().color().name());
    _config->setValue("keyword/color", _keywordFormat.foreground().color().name());
    _config->setValue("keyword/list", QVariant(_keywords));
    _config->setValue("function/color", _functionFormat.foreground().color().name());
    _config->setValue("class/color", _classFormat.foreground().color().name());
    _config->setValue("quotation/color", _quotationFormat.foreground().color().name());
    _config->setValue("singleline_comment/color", _singleLineCommentFormat.foreground().color().name());
    _config->setValue("multiline_comment/color", _multiLineCommentFormat.foreground().color().name());
    _config->sync();
}

void PasHighlighter::highlightBlock(const QString& text)
{
    // singleline matches
    foreach (const HighlightingRule& rule, _highlightingRules) {
        QRegExp expression(rule.pattern);
        int index = expression.indexIn(text);
        while (index >= 0) {
            int length = expression.matchedLength();
            setFormat(index, length, rule.format);
            index = expression.indexIn(text, index + length);
        }
    }

    /* multiline matches
     * block-state:
     * 0 = comment-block-start-or-end, 1 = comment-block-not-end
     */
    setCurrentBlockState(0);

    int startIndex = 0;
    if (previousBlockState() != 1)
        startIndex = _commentStartExpression.indexIn(text);

    while (startIndex >= 0) {
        int endIndex = _commentEndExpression.indexIn(text, startIndex);
        int commentLength;
        if (endIndex == -1) {
            setCurrentBlockState(1);
            commentLength = text.length() - startIndex;
        } else {
            commentLength = endIndex - startIndex + _commentEndExpression.matchedLength();
        }
        setFormat(startIndex, commentLength, _multiLineCommentFormat);
        startIndex = _commentStartExpression.indexIn(text, startIndex + commentLength);
    }
}
